#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2010 - 2023, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# We kindly request you to use one or more of the following phrases to refer to
# foxBMS in your hardware, software, documentation or advertising materials:
#
# - "This product uses parts of foxBMS®"
# - "This product includes parts of foxBMS®"
# - "This product is derived from foxBMS®"

"""Main Build Script: ``./wscript``
================================

This script defines how to configure and build the project. This includes
configuration the toolchain for building foxBMS binaries, the documentation
and running various checks on the source files.
"""


import os
import pathlib
import shlex
import sys
import tarfile
import linecache
import stat
from binascii import hexlify
import dataclasses
from typing import Union

import jsonschema
from waflib import Build, Configure, Context, Errors, Logs, Options, Scripting, Utils
from waflib.Build import (
    BuildContext,
    CleanContext,
    InstallContext,
    ListContext,
    StepContext,
)

Context.Context.line_just = 50
Configure.autoconfig = 1

out = "build"  # pylint:disable=invalid-name
"""output directory"""
top = "."  # pylint:disable=invalid-name
"""Waf top directory"""

APPNAME = "foxBMS"
"""name of the application. This is used in various Waf functions"""

VERSION = "1.6.0"
"""version of the application. This is used in various Waf functions. This
version must match the version number defined in ``macros.txt``. Otherwise a
configuration error is thrown."""

BIN_VARIANTS = ["bin", "axivion"]
"""Binary build command variations that are supported. The commands are then
generated by concatenating the command + the variant, e.g., ``build_bin``"""

MISC_VARIANTS = ["docs", "unit_test"]
"""Additional commands, that do not need more contexts than build and clean"""

ALL_VARIANTS = {"binary": BIN_VARIANTS, "misc": MISC_VARIANTS}

TOOLDIR = os.path.join("tools", "waf-tools")

BMS_CONFIG = os.path.join("conf", "bms", "bms.json")


@dataclasses.dataclass
class FoxBMSDefine:
    """container for defines"""

    name: str
    value: Union[int, str, None] = 0


AFE_SETUP = {
    "fsm": FoxBMSDefine("FOXBMS_AFE_DRIVER_TYPE_FSM", 0),
    "no-fsm": FoxBMSDefine("FOXBMS_AFE_DRIVER_TYPE_NO_FSM", 0),
    "afe-ic": FoxBMSDefine("", None),
}

for target_type, target_val in ALL_VARIANTS.items():
    contexts = (BuildContext, CleanContext)
    if target_type == "binary":
        contexts += (ListContext, StepContext)
    for var in target_val:
        # save contexts
        old_contexts = contexts
        if var == "bin":
            contexts += (InstallContext,)
        for cont in contexts:
            # pylint: disable=invalid-name
            name = cont.__name__.replace("Context", "").lower()

            # pylint:disable=invalid-name,too-many-ancestors,too-few-public-methods
            class tmp_1(cont):
                """Helper class to create the build variant commands"""

                if name == "build":
                    __doc__ = f"executes the {name} of {var}"
                elif name == "install":
                    __doc__ = f"flash {var} to the target"
                elif name == "clean":
                    __doc__ = f"cleans the project {var}"
                elif name == "list":
                    __doc__ = f"lists the targets to execute for {var}"
                elif name == "step":
                    __doc__ = f"executes tasks in a step-by-step fashion, for debugging of {var}"
                cmd = str(name) + "_" + var
                variant = var

        # restore contexts
        contexts = old_contexts


BUILD_VARIANTS = []
CLEAN_VARIANTS = []
# build and clean variants exist for all commands
for target_type, target_val in ALL_VARIANTS.items():
    for var in target_val:
        BUILD_VARIANTS.append(f"build_{var}")
        CLEAN_VARIANTS.append(f"clean_{var}")

DIST_EXCLUDE = (
    f"{out}/** **/.git **/.gitignore .gitlab/** **/.gitattributes "
    "**/*.tar.bz2 **/*.tar.gz **/*.pyc __pycache__ "
    "tools/waf*.*.**-* .lock-* "
    f".ws *eclipse* .vs* { APPNAME.lower()}/**"
)
"""Files and directories that are excluded when running dist commands"""


def version_consistency_checker(ctx):
    """checks that all version strings in the repository are synced"""
    doc_dir = "docs"
    changelog_file = ctx.path.find_node(
        os.path.join(doc_dir, "general", "changelog.rst")
    )
    changelog_txt = changelog_file.read(encoding="utf-8")
    m_file = ctx.path.find_node(os.path.join(doc_dir, "macros.txt"))
    m_file_txt = m_file.read(encoding="utf-8")
    gs_file = ctx.path.find_node(
        os.path.join(doc_dir, "getting-started", "software-installation.rst")
    )
    gs_file_txt = gs_file.read()
    if m_file_txt.find(f".. |version_foxbms| replace:: ``{VERSION}``") < 0:
        ctx.fatal(
            f"The version information in {m_file} is different from the "
            f"specified version {VERSION}."
        )
    if changelog_txt.find(f"[{VERSION}]") < 0:
        ctx.fatal(
            f"The version information in {changelog_file} is different "
            f"from the specified version {VERSION}."
        )
    repo_url = "https://github.com/foxBMS/foxbms-2"
    must_include_version = [
        f"curl --silent --show-error -L -o foxbms-2-v{VERSION}.zip "
        f"{repo_url}/archive/v{VERSION}.zip",
        f"tar -x -f foxbms-2-v{VERSION}.zip",
        f"ren foxbms-2-{VERSION} foxbms-2",
    ]
    if not all(gs_file_txt.find(i) > 0 for i in must_include_version):
        ctx.fatal(
            f"The version information in {gs_file} is different from the "
            f"specified version {VERSION}"
        )
    pys = [
        ctx.path.find_node(os.path.join("tools", "gui", "fgui", "__init__.py")),
    ]
    if not all(i.read().find(f'__version__ = "{VERSION}"') > 0 for i in pys):
        ctx.fatal(f"Version information in {pys} is not correct.")
    all_c_sources = ctx.path.ant_glob(
        "docs/**/*.c docs/**/*.h src/**/*.c src/**/*.c tests/**/*.c tests/**/*.c",
        excl=[
            "tests/axivion/addon-test/**/*.c",
            "tests/axivion/addon-test/**/*.h",
            "tests/axivion/compiler-errata/ti-cgt-arm_20.2.6.lts/**/*.c",
            "tests/axivion/compiler-errata/ti-cgt-arm_20.2.6.lts/**/*.h",
            "tests/axivion/qualification-test/**/*.c",
            "tests/axivion/qualification-test/**/*.h",
            "tests/unit/build/**",
        ],
    )
    version_line = -1
    main_txt = ctx.path.find_node("src/app/main/main.c").read()
    for i, line in enumerate(main_txt.splitlines()):
        if line.startswith(" * @version "):
            version_line = i + 1
            break
    expected_line = f"* @version v{VERSION}"
    for i in all_c_sources:
        version_line_txt = linecache.getline(i.abspath(), version_line)
        if version_line_txt.startswith(" * @version "):
            if not version_line_txt.strip() == expected_line:
                ctx.fatal(
                    f"Version information in {i.abspath()}:{version_line} is "
                    f"not correct (expected '{expected_line}', but found "
                    f"'{version_line_txt.strip()}')."
                )


def options(opt):
    """Defines options that can be passed to waf"""
    opt.add_option(
        "--coverage",
        action="store_true",
        help="Builds a coverage report based on the unit test",
    )
    opt.load("f_axivion", tooldir=TOOLDIR)
    opt.load("f_sphinx_build", tooldir=TOOLDIR)
    opt.load("doxygen", tooldir=TOOLDIR)
    opt.load("f_ti_arm_cgt", tooldir=TOOLDIR)
    # load db-check-tool
    opt.load("f_check_db_vars", tooldir=TOOLDIR)
    # load bootstrap-library-project-tool
    opt.load("f_bootstrap_library_project", tooldir=TOOLDIR)
    opt.load("f_guidelines", tooldir=TOOLDIR)

    for k in (
        "--targets",
        "--out",
        "--top",
        "--prefix",
        "--destdir",
        "--bindir",
        "--libdir",
        "--msvc_version",
        "--msvc_targets",
        "--no-msvc-lazy",
        "--force",
        "--check-c-compiler",
        "doxygen",
    ):
        option = opt.parser.get_option(k)
        if option:
            opt.parser.remove_option(k)

    Context.classes.remove(Build.UninstallContext)

    opt.add_option(
        "--confcache",
        dest="confcache",
        default=0,
        action="count",
        help="Use a configuration cache",
    )

    opt.add_option(
        "--skip-doxygen",
        action="store_true",
        help="Builds the documentation without the Doxygen documentation",
    )

    opt.load("f_miniconda_env", tooldir=TOOLDIR)
    opt.load("f_lauterbach", tooldir=TOOLDIR)
    opt.add_option(
        "--why", dest="WHY", action="store_true", help="Loads the 'why' tool."
    )
    opt.load("f_j_flash", tooldir=TOOLDIR)
    opt.load("f_git_hooks", tooldir=TOOLDIR)


def configure(conf):  # pylint: disable=too-many-statements,too-many-branches
    """Configures the project.
    This includes loading all tools needed to build and link binaries,
    rendering the documentation and checking source files for style violations:
    The loaded tools are:

    - TI ARM compiler (`f_ti_arm_cgt`)
    - Documentation tools (`f_sphinx_build`, `doxygen`)

    To ensure that the active environment is the correct one for the project,
    all conda packages are checked to be installed in the correct version.

    A workspace is generated if Visual Studio Code is found on the machine.
    """
    if " " in conf.path.abspath():
        conf.fatal(f"Project path must not contain spaces ({conf.path}).")
    conf.env.append_unique("PROJECT_ROOT", pathlib.Path(conf.path.abspath()).as_posix())
    known_max_depth = 133
    expected_max_path_depth = len(conf.path.abspath()) + known_max_depth
    if Utils.is_win32 and expected_max_path_depth > 260:
        conf.fatal(
            "Build path length will exceed 260 characters.\nClone or move the "
            "repository into a shorter path."
        )
    else:
        Logs.debug(f"Expected max path depth: {expected_max_path_depth}")
    conf.msg("Checking project path", conf.path.abspath())

    version_consistency_checker(conf)
    conf.load("f_helpers", tooldir=TOOLDIR)
    conf.find_program("git", var="GIT")
    conf.load("f_node_helper", tooldir=TOOLDIR)
    conf.load("f_ti_arm_cgt", tooldir=TOOLDIR)
    fragment = "#include <stdint.h>\n\nint main() {\n    return 0;\n}\n"
    conf.check(
        features="c", fragment=fragment, msg="Checking for code snippet (object)"
    )

    fragment = "#include <stdint.h>\n\nint sum(int a, int b){\n    return (a + b);}\n"
    conf.check(
        features="c cstlib",
        fragment=fragment,
        msg="Checking for code snippet (library)",
    )

    def full_build(bld):
        bld.env.APPNAME = "TEST_BUILD"
        c_fragment = "#include <stdint.h>\n\nint main() {\n    return 0;\n}\n"
        h_fragment = (
            "#ifndef GENERAL_H_\n#define GENERAL_H_\n#include <stdbool.h>\n"
            "#include <stdint.h>\n#endif /* GENERAL_H_ */\n"
        )
        source = bld.srcnode.make_node("test.c")
        source.parent.mkdir()
        source.write(c_fragment, encoding="utf-8")
        include = bld.srcnode.make_node("general.h")
        include.write(h_fragment, encoding="utf-8")
        linker_script = bld.path.find_node(
            os.path.join("..", "..", "src", "app", "main", "linker_script_elf.cmd")
        )
        version_header = bld.path.find_node(
            os.path.join(
                "..", "..", "src", "app", "main", "include", "config", "version_cfg.h"
            )
        )
        cflags = []
        if bld.env.RTSV_missing:
            cflags = ["--diag_remark=10366"]
        linker_pulls = bld.path.find_or_declare("linker_pulls.json")
        linker_pulls.write("{}\n")
        bld.tiprogram(
            includes=[include.parent, version_header.parent],
            source=[source],
            cflags=cflags,
            linker_script=linker_script,
            no_version=True,
            linker_pulls=linker_pulls,
        )

    default_env = conf.env
    test_env = conf.env.derive()
    test_env.detach()

    conf.setenv("test_env", test_env)
    rtsv_lib = "rtsv7R4_A_be_v3D16_eabi.lib"
    rtsv_lib_path = os.path.join(
        pathlib.Path(conf.env.get_flat("CC")).parent.parent.absolute(),
        "lib",
        rtsv_lib,
    )
    if not os.path.isfile(rtsv_lib_path):
        Logs.warn(
            f"Runtime support library '{rtsv_lib}' missing. Need to build "
            "it first. The next step may take a while..."
        )
        conf.env.RTSV_missing = True
    else:
        conf.env.RTSV_missing = False
    conf.env.STLIB = ["c"]
    conf.env.TARGETLIB = []
    if "--undef_sym=resetEntry" in conf.env.LINKFLAGS:
        conf.env.LINKFLAGS.remove("--undef_sym=resetEntry")
    conf.check(msg="Checking for code snippet (program)", build_fun=full_build)
    conf.setenv("", default_env)

    conf.load("f_miniconda_env", tooldir=TOOLDIR)
    conf.load("f_check_db_vars", tooldir=TOOLDIR)

    conf.load("f_bootstrap_library_project", tooldir=TOOLDIR)
    conf.load("f_guidelines", tooldir=TOOLDIR)

    # add flasher tool
    conf.load("f_j_flash", tooldir=TOOLDIR)

    # configure the documentation toolchain
    conf.load("f_sphinx_build", tooldir=TOOLDIR)
    conf.load("doxygen", tooldir=TOOLDIR)
    conf.load("f_unit_test", tooldir=TOOLDIR)
    conf.env.VSCODE_MK_DIRS = [
        os.path.join(out, "unit_test", "test", "mocks"),
        os.path.join(out, "bin", "src", "app", "main"),
        os.path.join(out, "bin", "src", "hal", "include"),
        os.path.join(out, "bin", "src", "hal", "source"),
    ]
    conf.load("f_ozone", tooldir=TOOLDIR)
    conf.load("f_lauterbach", tooldir=TOOLDIR)
    conf.load("f_axivion", tooldir=TOOLDIR)

    # Configure the build for the correct RTOS
    bms_config_node = conf.path.find_node(BMS_CONFIG)
    conf.env.append_unique(
        "CONFIG_BMS_JSON_HASH", hexlify(bms_config_node.h_file()).decode("utf-8")
    )
    bms_config = bms_config_node.read_json()

    validator = conf.f_validator(
        conf.path.find_node(
            os.path.join("conf", "bms", "schema", "bms.schema.json")
        ).abspath()
    )
    try:
        validator.validate(bms_config)
    except jsonschema.exceptions.ValidationError as err:
        good_values = ", ".join([f"'{i}'" for i in err.validator_value])
        conf.fatal(
            f"Setting '{err.instance}' in '{'/'.join(list(err.path))}' is not "
            f"supported.\nUse one of these: {good_values}."
        )

    # parse conf/bms/bms.json to get all required defines, includes etc.
    # needs to be done, prior to loading the VS Code tool!
    # AFE on Slave unit: bms.json:slave-unit:analog-front-end
    slave_afe = bms_config["slave-unit"]["analog-front-end"]
    afe_man = slave_afe["manufacturer"]
    afe_ic = slave_afe["ic"]
    conf.env.afe_manufacturer = afe_man
    conf.env.afe_ic = afe_ic
    # vendor/ic includes and foxBMS specific driver adaptions
    afe_ic_inc = slave_afe["ic"]
    afe_driver_type = "fsm"
    afe_ic_d = None
    if slave_afe["manufacturer"] == "ltc":
        if slave_afe["ic"] in ("6804-1", "6811-1", "6812-1"):
            afe_ic_inc = "6813-1"
        if slave_afe["ic"] == "6804-1":
            afe_ic_d = "LTC_LTC6804_1"
        elif slave_afe["ic"] == "6806":
            afe_ic_d = "LTC_LTC6806"
        elif slave_afe["ic"] == "6811-1":
            afe_ic_d = "LTC_LTC6811_1"
        elif slave_afe["ic"] == "6812-1":
            afe_ic_d = "LTC_LTC6812_1"
        elif slave_afe["ic"] == "6813-1":
            afe_ic_d = "LTC_LTC6813_1"
    elif slave_afe["manufacturer"] == "nxp":
        afe_driver_type = "no-fsm"
        if slave_afe["ic"] == "mc33775a":
            afe_ic_d = "NXP_MC33775A"
    elif slave_afe["manufacturer"] == "adi":
        afe_driver_type = "no-fsm"
        if slave_afe["ic"] == "ades1830":
            afe_ic_d = "ADI_ADES1830"
    elif slave_afe["manufacturer"] == "debug":
        if slave_afe["ic"] == "default":
            afe_ic_d = "DEBUG_DEFAULT"
    elif slave_afe["manufacturer"] == "maxim":
        if slave_afe["ic"] == "max17852":
            afe_ic_d = "MAX_MAX17852"
    elif slave_afe["manufacturer"] == "ti":
        if slave_afe["ic"] == "dummy":
            afe_ic_d = "TI_DUMMY"

    if not afe_ic_d:
        conf.fatal("AFE IC specific define not set.")
    # set the driver type implementation accordingly
    AFE_SETUP[afe_driver_type].value = 1
    AFE_SETUP["afe-ic"].name = "FOXBMS_AFE_DRIVER_" + afe_ic_d
    AFE_SETUP["afe-ic"].value = 1
    for _, i in AFE_SETUP.items():
        conf.define(i.name, i.value)

    # get AFE includes
    afe_base_path = os.path.join("src", "app", "driver", "afe")
    incs = os.path.join(
        afe_base_path, afe_man, afe_ic_inc, f"{afe_man}_{afe_ic_inc}.json"
    )
    afe_details = conf.path.find_node(incs).read_json()
    afe_includes = [
        os.path.join(afe_base_path, afe_man, afe_ic_inc, i)
        for i in afe_details["include"]
    ]
    for i in afe_includes:
        if not os.path.isdir(i):
            conf.fatal(f"'{i}' does not exist.")
    conf.env.append_unique(
        "INCLUDES_AFE", [conf.path.find_node(i).abspath() for i in afe_includes]
    )
    # temperature sensor on Slave unit: bms.json:slave-unit:temperature-sensor
    slave_temp = bms_config["slave-unit"]["temperature-sensor"]
    conf.env.temperature_sensor_manuf = slave_temp["manufacturer"]
    conf.env.temperature_sensor_model = slave_temp["model"]
    conf.env.temperature_sensor_meth = slave_temp["method"]

    # application setting: bms.json:application
    # state estimation
    app_cfg = bms_config["application"]
    state_estimators = app_cfg["algorithm"]["state-estimation"]
    conf.env.state_estimator_soc = state_estimators["soc"]
    conf.env.state_estimator_soe = state_estimators["soe"]
    conf.env.state_estimator_sof = state_estimators["sof"]
    conf.env.state_estimator_soh = state_estimators["soh"]

    # balancing strategy
    conf.env.balancing_strategy = app_cfg["balancing-strategy"]
    # ltc 6806 (fuel cell monitoring ic) has no balancing support
    if (
        afe_man == "ltc"
        and afe_ic == "6806"
        and not conf.env.balancing_strategy == "none"
    ):
        conf.fatal(f"{afe_man.upper()} {afe_ic} does not support balancing.")

    # insulation-monitoring-device
    imd_cfg = app_cfg["insulation-monitoring-device"]
    conf.env.imd_manufacturer = imd_cfg["manufacturer"]
    conf.env.imd_model = imd_cfg["model"]
    if conf.env.imd_manufacturer:
        conf.env.append_unique(
            "INCLUDES_IMD",
            [
                conf.path.find_node(i)
                for i in [
                    conf.env.imd_manufacturer + conf.env.imd_model,
                ]
            ],
        )

    # rtos: bms.json:rtos
    rtos_name = bms_config["rtos"]["name"]
    rtos_base_path = os.path.join("src", "os", rtos_name)
    conf.env.append_unique("RTOS_NAME", rtos_name)
    rtos_details = conf.path.find_node(
        os.path.join(rtos_base_path, f"{rtos_name}_cfg.json")
    ).read_json()

    rtos_includes = [os.path.join(rtos_base_path, i) for i in rtos_details["include"]]
    conf.env.append_unique(
        "INCLUDES_RTOS", [conf.path.find_node(i).abspath() for i in rtos_includes]
    )

    conf.define(f"FOXBMS_USES_{bms_config['rtos']['name'].upper()}", 1)

    # load VS Code setup as last foxBMS specific tool to ensure that all
    # variables have a meaningful value
    conf.load("f_vscode", tooldir=TOOLDIR)
    conf.load("f_git_hooks", tooldir=TOOLDIR)

    # the project has been successfully configured, now we can set the
    # application name and version
    conf.env.APPNAME = APPNAME
    conf.env.VERSION = VERSION
    if conf.options.WHY:
        conf.load("why", tooldir=TOOLDIR)


def build(bld):  # pylint: disable=too-many-branches,too-many-statements
    """High level definition of the build details"""
    if not bld.variant:
        bld.fatal(
            f"A {bld.cmd} variant must be specified. The build variants are: "
            f"{', '.join(BUILD_VARIANTS)}.\nFor more details run 'python "
            f"tools{os.sep}waf --help'"
        )
    # we need to patch the build instructions for the Axivion build, and by
    # that the "normal" build using TI ARM CGT gets broken (only in that
    # context!), therefore (build|clean)_axivion must only be used as last
    # build commands if multiple commands are supplied.
    all_commands = [bld.cmd] + Options.commands  # current command + remaining
    if any(x in all_commands for x in ["build_axivion", "clean_axivion"]):
        b_idx = sys.maxsize
        try:
            b_idx = all_commands.index("build_axivion")
        except ValueError:
            pass
        c_idx = sys.maxsize
        try:
            c_idx = all_commands.index("clean_axivion")
        except ValueError:
            pass
        min_idx = min([b_idx, c_idx])
        ax_commands = all_commands[min_idx:]
        err = 0
        for i in ax_commands:
            if not "_axivion" in i:
                err += 1
                Logs.error(f"'{i}' must not be used in that order {all_commands!r}.")
        if err:
            bld.fatal(
                "Axivion related commands must be moved to the end of the "
                "command list, i.e. all other build commands must precede the "
                "axivion commands."
            )
    version_consistency_checker(bld)
    bld.env.append_unique(
        "CMD_FILES",
        [bld.path.find_node(os.path.join("conf", "cc", "remarks.txt")).abspath()],
    )
    if not bld.env.CONFIG_BMS_JSON_HASH[0] == hexlify(
        bld.path.find_node(BMS_CONFIG).h_file()
    ).decode("utf-8"):
        bld.fatal(f"{BMS_CONFIG} has changed. Please run the configure command again.")
    if bld.variant == "bin":
        bld.recurse("src")

    if bld.variant == "axivion":
        if not bld.env.AXIVION_CC:
            Logs.warn("Axivion tools not available.")
            return
        bld.patch_for_axivion_build(bld)

        bld.recurse("src")

    if bld.variant == "unit_test":
        Options.commands = ["check_test_files"] + Options.commands
        if bld.cmd.startswith("clean"):
            return
        if bld.cmd.startswith("build"):
            if not bld.env.CEEDLING:
                bld.fatal("Can not run unit tests as ceedling is missing.")
            if not bld.env.GCC:
                bld.fatal("Can not run unit tests as gcc is missing.")
            if not bld.env.GCOV:
                bld.fatal("Can not run unit tests as gcov is missing.")
            if not bld.env.GCOVR:
                bld.fatal("Can not run unit tests as gcovr is missing.")

        bld(
            features="db_check",
            files=bld.path.ant_glob("tests/unit/**/*.c"),
        )
        bld.add_group()
        source = bld.path.find_node(bld.env.CEEDLING_MAIN_PROJECT_FILE)
        bld(
            features="subst",
            source=source,
            target="project.yml",
            is_copy=True,
        )
        source = bld.path.find_node(bld.env.CEEDLING_CMD_FILE)
        bld(
            features="subst",
            source=source,
            target=source.name,
            is_copy=True,
            chmod=os.stat(source.abspath()).st_mode | stat.S_IEXEC,
        )
        if Utils.is_win32:
            bld(
                source=os.path.join("conf", "hcg", "hcg.hcg"),
                unit_test=True,
                startup_hash=bld.path.find_node(
                    os.path.join("src", "hal", "startup.hash")
                ),
            )
        else:
            Logs.warn(
                "HALCoGen not available. Assuming generated sources are available otherwise."
            )
        bld.add_group()
        bld(features="ceedling")

    if bld.variant == "docs":
        # The jinja2 templates generate the specific template files for documentation.
        # At next doxygen is run, to ensure that doxygen's xml output is build.
        # After that the regular documentation can be build using sphinx-build, which
        # now includes the build documentation as well as the API documentation from
        # doxygen.
        bld.recurse(
            [
                os.path.join("docs", "developer-manual", "style-guide", "examples"),
                os.path.join(
                    "docs", "developer-manual", "style-guide", "state-machine-example"
                ),
                os.path.join("docs", "software", "modules", "driver", "can"),
                os.path.join("docs", "software", "modules", "engine", "database"),
                os.path.join("docs", "software", "modules", "task", "ftask"),
            ],
        )
        doc_dir = "docs"
        bld.post_mode = Build.POST_LAZY

        bld.add_group("generate_doc_files")
        bld.add_group("doxygen")
        bld.add_group("sphinx")

        bld.set_group("generate_doc_files")
        # we use absolute paths for the doxygen configuration as it is written
        # during configuration time, and therefore this is okay
        # fmt: off
        # pylint: disable=line-too-long
        _input = [
            bld.path.find_node(os.path.join("docs", "developer-manual", "style-guide", "state-machine-example")),
            bld.path.find_node("src")
        ]
        _project_logo = bld.path.find_node(os.path.join("docs", "_static", "foxbms250px.png"))
        _exclude = [
            bld.path.find_node(os.path.join("src", "hal")),
            bld.path.find_node(os.path.join("src", "os")),
            bld.path.find_node(os.path.join("src", "app", "driver", "afe", "ltc", "common", "ltc_pec.c")),
            bld.path.find_node(os.path.join("src", "app", "driver", "afe", "ltc", "common", "ltc_pec.h")),
            bld.path.find_node(os.path.join("src", "app", "driver", "afe", "nxp", "mc33775a","vendor")),
            bld.path.find_node(os.path.join("tests", "unit", "build")),
        ]
        _html_footer = bld.path.find_node(os.path.join("docs", "doxygen_footer.html"))
        _layout_file = bld.path.find_node(os.path.join("docs", "doxygen_layout.xml"))
        _html_style_sheet = bld.path.find_node(os.path.join("docs", "style-sheet-file.css"))
        _html_extra_files = bld.path.find_node(os.path.join("docs", "_static", "cc.large.png"))
        _image_path = bld.path.find_node(os.path.join("docs", "_static", "cc.large.png"))
        # pylint: enable=line-too-long
        # fmt: on
        if not all(
            (
                _input,
                _project_logo,
                _exclude[:],
                _html_footer,
                _layout_file,
                _html_style_sheet,
                _html_extra_files,
                _image_path,
            )
        ):
            bld.fatal("Some doxygen input is not correct.")
        doxy_conf_src = bld.path.get_bld().find_or_declare("doxygen_src.conf")
        bld(
            features="subst",
            source=bld.path.find_node(os.path.join("docs", "doxygen_src.conf.in")),
            target=doxy_conf_src,
            PROJECT_NAME=APPNAME,
            PROJECT_NUMBER=VERSION,
            PROJECT_BRIEF=f'"The {APPNAME} Battery Management System API Documentation"',
            PROJECT_LOGO=_project_logo.abspath(),
            OUTPUT_DIRECTORY="_static/doxygen/src",
            INPUT=" ".join([i.abspath() for i in _input if i]),
            EXCLUDE=" ".join([i.abspath() for i in _exclude if i]),
            HTML_FOOTER=_html_footer.abspath(),
            LAYOUT_FILE=_layout_file.abspath(),
            HTML_STYLESHEET=_html_style_sheet.abspath(),
            HTML_EXTRA_FILES=_html_extra_files.abspath(),
            IMAGE_PATH=_image_path.abspath(),
        )
        _input = [
            bld.path.find_node("src/app"),
            bld.path.find_node("tests/unit"),
        ]
        doxy_conf_tests = bld.path.get_bld().find_or_declare("doxygen_tests.conf")
        bld(
            features="subst",
            source=bld.path.find_node(os.path.join("docs", "doxygen_tests.conf.in")),
            target=doxy_conf_tests,
            PROJECT_NAME=f"{APPNAME} - Unit Tests",
            PROJECT_NUMBER=VERSION,
            PROJECT_BRIEF=f'"The {APPNAME} Unit Tests API Documentation"',
            PROJECT_LOGO=_project_logo.abspath(),
            OUTPUT_DIRECTORY="_static/doxygen/tests",
            INPUT=" ".join([i.abspath() for i in _input if i]),
            EXCLUDE=" ".join([i.abspath() for i in _exclude if i]),
            HTML_FOOTER=_html_footer.abspath(),
            LAYOUT_FILE=_layout_file.abspath(),
            HTML_STYLESHEET=_html_style_sheet.abspath(),
            HTML_EXTRA_FILES=_html_extra_files.abspath(),
            IMAGE_PATH=_image_path.abspath(),
        )
        bld.set_group("doxygen")
        doxy_conf_src = bld.path.get_bld().find_or_declare("doxygen_src.conf")
        doxy_conf_tests = bld.path.get_bld().find_or_declare("doxygen_tests.conf")
        if not bld.options.skip_doxygen:
            bld(features="doxygen", doxygen_conf=doxy_conf_src)
            bld(features="doxygen", doxygen_conf=doxy_conf_tests)

        bld.set_group("sphinx")
        # fmt: off
        # pylint: disable=line-too-long
        sources = [
            os.path.join(doc_dir, "developer-manual", "hardware-developer-manual.rst"),
            os.path.join(doc_dir, "developer-manual", "preface.rst"),
            os.path.join(doc_dir, "developer-manual", "public-release-process.rst"),
            os.path.join(doc_dir, "developer-manual", "software", "software-development-process.rst"),
            os.path.join(doc_dir, "developer-manual", "software", "software-modifications.rst"),
            os.path.join(doc_dir, "developer-manual", "software", "software-programming-language.rst"),
            os.path.join(doc_dir, "developer-manual", "software", "software-testing.rst"),
            os.path.join(doc_dir, "developer-manual", "software", "software-tools.rst"),
            os.path.join(doc_dir, "developer-manual", "software", "software-verification.rst"),
            os.path.join(doc_dir, "developer-manual", "software-developer-manual.rst"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "guidelines_batch_shell.rst"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "guidelines_c.rst"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "guidelines_general.rst"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "guidelines_overview.rst"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "guidelines_overview_c.csv"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "guidelines_overview_general.csv"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "guidelines_overview_python.csv"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "guidelines_overview_rst.csv"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "guidelines_overview_sh.csv"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "guidelines_overview_yaml.csv"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "guidelines_python.rst"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "guidelines_rst.rst"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "guidelines_yaml.rst"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "state-machines_how-to.rst"),
            os.path.join(doc_dir, "developer-manual", "style-guide", "style-guide.rst"),
            os.path.join(doc_dir, "general", "changelog.rst"),
            os.path.join(doc_dir, "general", "commit-msgs", "next-release.txt"),
            os.path.join(doc_dir, "general", "commit-msgs", "release-v1.0.0.txt"),
            os.path.join(doc_dir, "general", "commit-msgs", "release-v1.0.1.txt"),
            os.path.join(doc_dir, "general", "commit-msgs", "release-v1.0.2.txt"),
            os.path.join(doc_dir, "general", "commit-msgs", "release-v1.1.0.txt"),
            os.path.join(doc_dir, "general", "commit-msgs", "release-v1.1.1.txt"),
            os.path.join(doc_dir, "general", "commit-msgs", "release-v1.1.2.txt"),
            os.path.join(doc_dir, "general", "commit-msgs", "release-v1.2.0.txt"),
            os.path.join(doc_dir, "general", "commit-msgs", "release-v1.2.1.txt"),
            os.path.join(doc_dir, "general", "commit-msgs", "release-v1.3.0.txt"),
            os.path.join(doc_dir, "general", "commit-msgs", "release-v1.4.0.txt"),
            os.path.join(doc_dir, "general", "commit-msgs", "release-v1.4.1.txt"),
            os.path.join(doc_dir, "general", "commit-msgs", "release-v1.5.0.txt"),
            os.path.join(doc_dir, "general", "commit-msgs", "release-v1.5.1.txt"),
            os.path.join(doc_dir, "general", "license.rst"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_ceedling.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_freertos.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_llvm.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_mingw64.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_miniconda.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_packages-conda-env-linux.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_packages-conda-env-win32.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_ruby-installer.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_ti-ccs.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_ti-hcg.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_vs-code.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_vs-code_extensions.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_waf-binary.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-info_waf-unit-tests.csv"),
            os.path.join(doc_dir, "general", "license-tables", "license-packages-conda-env-spelling.txt"),
            os.path.join(doc_dir, "general", "license-tables", "license-packages-conda-env-spelling-build-strings.txt"),
            os.path.join(doc_dir, "general", "motivation.rst"),
            os.path.join(doc_dir, "general", "releases.csv"),
            os.path.join(doc_dir, "general", "releases.rst"),
            os.path.join(doc_dir, "general", "safety", "safety.rst"),
            os.path.join(doc_dir, "general", "team.rst"),
            os.path.join(doc_dir, "general", "team-ad-sc.rst"),
            os.path.join(doc_dir, "general", "team-dev.rst"),
            os.path.join(doc_dir, "general", "team-former.rst"),
            os.path.join(doc_dir, "getting-started", "first-steps-on-hardware.rst"),
            os.path.join(doc_dir, "getting-started", "getting-started.rst"),
            os.path.join(doc_dir, "getting-started", "llvm-installation", "llvm-installation.rst"),
            os.path.join(doc_dir, "getting-started", "mingw64-installation", "mingw64-installation.rst"),
            os.path.join(doc_dir, "getting-started", "miniconda-installation", "conda-configuration.rst"),
            os.path.join(doc_dir, "getting-started", "miniconda-installation", "miniconda-installation.rst"),
            os.path.join(doc_dir, "getting-started", "repository-structure.rst"),
            os.path.join(doc_dir, "getting-started", "ruby-installation", "ruby-installation.rst"),
            os.path.join(doc_dir, "getting-started", "software-installation.rst"),
            os.path.join(doc_dir, "getting-started", "workspace.rst"),
            os.path.join(doc_dir, "hardware", "connectors.rst"),
            os.path.join(doc_dir, "hardware", "design-resources.rst"),
            os.path.join(doc_dir, "hardware", "hardware.rst"),
            os.path.join(doc_dir, "hardware", "interfaces", "ltc-ltc6820-vx.x.x", "ltc-ltc6820-changelog.rst"),
            os.path.join(doc_dir, "hardware", "interfaces", "ltc-ltc6820-vx.x.x", "ltc-ltc6820-v1.0.3", "ltc-ltc6820-v1.0.3_isospi_connectors.csv"),
            os.path.join(doc_dir, "hardware", "interfaces", "ltc-ltc6820-vx.x.x", "ltc-ltc6820-v1.0.3", "ltc-ltc6820-v1.0.3_master_connector.csv"),
            os.path.join(doc_dir, "hardware", "interfaces", "ltc-ltc6820-vx.x.x", "ltc-ltc6820-v1.0.3.rst"),
            os.path.join(doc_dir, "hardware", "interfaces", "maxim-max17841b-vx.x.x", "maxim-max17841b-changelog.rst"),
            os.path.join(doc_dir, "hardware", "interfaces", "maxim-max17841b-vx.x.x", "maxim-max17841b-v1.0.0", "maxim-max17841b-v1.0.0_debug_connector.csv"),
            os.path.join(doc_dir, "hardware", "interfaces", "maxim-max17841b-vx.x.x", "maxim-max17841b-v1.0.0", "maxim-max17841b-v1.0.0_master_connector.csv"),
            os.path.join(doc_dir, "hardware", "interfaces", "maxim-max17841b-vx.x.x", "maxim-max17841b-v1.0.0", "maxim-max17841b-v1.0.0_uartrx_connectors.csv"),
            os.path.join(doc_dir, "hardware", "interfaces", "maxim-max17841b-vx.x.x", "maxim-max17841b-v1.0.0", "maxim-max17841b-v1.0.0_uarttx_connectors.csv"),
            os.path.join(doc_dir, "hardware", "interfaces", "maxim-max17841b-vx.x.x", "maxim-max17841b-v1.0.0.rst"),
            os.path.join(doc_dir, "hardware", "interfaces", "nxp-mc33664-vx.x.x", "nxp-mc33664-changelog.rst"),
            os.path.join(doc_dir, "hardware", "interfaces", "nxp-mc33664-vx.x.x", "nxp-mc33664-v1.0.2.rst"),
            os.path.join(doc_dir, "hardware", "interfaces.rst"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-changelog.rst"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "pinout", "ti-tms570lc4357-v1.1.1_can1.csv"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "pinout", "ti-tms570lc4357-v1.1.1_can2.csv"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "pinout", "ti-tms570lc4357-v1.1.1_debug.csv"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "pinout", "ti-tms570lc4357-v1.1.1_ethernet.csv"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "pinout", "ti-tms570lc4357-v1.1.1_extension.csv"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "pinout", "ti-tms570lc4357-v1.1.1_interface.csv"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "pinout", "ti-tms570lc4357-v1.1.1_interlock.csv"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "pinout", "ti-tms570lc4357-v1.1.1_isomon.csv"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "pinout", "ti-tms570lc4357-v1.1.1_rs485.csv"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "pinout", "ti-tms570lc4357-v1.1.1_sps.csv"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "pinout", "ti-tms570lc4357-v1.1.1_supply_ext.csv"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "ti-tms570lc4357-v1.1.1_block_diagram.rst"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "ti-tms570lc4357-v1.1.1_functional_description.rst"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1", "ti-tms570lc4357-v1.1.1_pinout.rst"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.1.rst"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.3.rst"),
            os.path.join(doc_dir, "hardware", "master", "ti-tms570lc4357-vx.x.x", "ti-tms570lc4357-v1.1.5.rst"),
            os.path.join(doc_dir, "hardware", "master.rst"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6804-1-vx.x.x", "12-ltc-ltc6804-1-v1.x.x", "12-ltc-ltc6804-1-v1.x.x_cell_temperature_connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6804-1-vx.x.x", "12-ltc-ltc6804-1-v1.x.x", "12-ltc-ltc6804-1-v1.x.x_cell_voltage_connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6804-1-vx.x.x", "12-ltc-ltc6804-1-v1.x.x", "12-ltc-ltc6804-1-v1.x.x_master_daisy_connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6804-1-vx.x.x", "12-ltc-ltc6804-1-v1.x.x", "12-ltc-ltc6804-1-v1.x.x_primary_daisy_connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6804-1-vx.x.x", "12-ltc-ltc6804-1-v1.x.x.rst"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-changelog.rst"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.0.3", "12-ltc-ltc6811-1-v2.0.3_analog-inputs-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.0.3", "12-ltc-ltc6811-1-v2.0.3_cell-voltage-sense-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.0.3", "12-ltc-ltc6811-1-v2.0.3_daisy-input-connectors.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.0.3", "12-ltc-ltc6811-1-v2.0.3_daisy-output-connectors.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.0.3", "12-ltc-ltc6811-1-v2.0.3_digital-io-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.0.3", "12-ltc-ltc6811-1-v2.0.3_electrical-ratings.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.0.3", "12-ltc-ltc6811-1-v2.0.3_extension-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.0.3", "12-ltc-ltc6811-1-v2.0.3_external-dc-supply-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.0.3", "12-ltc-ltc6811-1-v2.0.3_gpio-extension-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.0.3", "12-ltc-ltc6811-1-v2.0.3_mechanical-dimensions.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.0.3", "12-ltc-ltc6811-1-v2.0.3_temperature-sensor-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.0.3.rst"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.2", "12-ltc-ltc6811-1-v2.1.2_analog-inputs-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.2", "12-ltc-ltc6811-1-v2.1.2_cell-voltage-sense-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.2", "12-ltc-ltc6811-1-v2.1.2_daisy-input-connectors.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.2", "12-ltc-ltc6811-1-v2.1.2_daisy-output-connectors.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.2", "12-ltc-ltc6811-1-v2.1.2_digital-io-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.2", "12-ltc-ltc6811-1-v2.1.2_electrical-ratings.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.2", "12-ltc-ltc6811-1-v2.1.2_extension-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.2", "12-ltc-ltc6811-1-v2.1.2_external-dc-supply-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.2", "12-ltc-ltc6811-1-v2.1.2_gpio-extension-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.2", "12-ltc-ltc6811-1-v2.1.2_mechanical-dimensions.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.2", "12-ltc-ltc6811-1-v2.1.2_temperature-sensor-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.2.rst"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.6", "12-ltc-ltc6811-1-v2.1.6_analog-inputs-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.6", "12-ltc-ltc6811-1-v2.1.6_cell-voltage-sense-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.6", "12-ltc-ltc6811-1-v2.1.6_daisy-input-connectors.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.6", "12-ltc-ltc6811-1-v2.1.6_daisy-output-connectors.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.6", "12-ltc-ltc6811-1-v2.1.6_digital-io-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.6", "12-ltc-ltc6811-1-v2.1.6_electrical-ratings.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.6", "12-ltc-ltc6811-1-v2.1.6_extension-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.6", "12-ltc-ltc6811-1-v2.1.6_external-dc-supply-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.6", "12-ltc-ltc6811-1-v2.1.6_gpio-extension-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.6", "12-ltc-ltc6811-1-v2.1.6_mechanical-dimensions.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.6", "12-ltc-ltc6811-1-v2.1.6_temperature-sensor-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "12-ltc-ltc6811-1-vx.x.x", "12-ltc-ltc6811-1-v2.1.6.rst"),
            os.path.join(doc_dir, "hardware", "slaves", "14-nxp-mc33775a-vx.x.x", "14-nxp-mc33775a-changelog.rst"),
            os.path.join(doc_dir, "hardware", "slaves", "14-nxp-mc33775a-vx.x.x", "14-nxp-mc33775a-v1.0.0", "14-nxp-mc33775a-v1.0.0_mechanical-dimensions.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "14-nxp-mc33775a-vx.x.x", "14-nxp-mc33775a-v1.0.0.rst"),
            os.path.join(doc_dir, "hardware", "slaves", "16-adi-ades1830-vx.x.x", "16-adi-ades1830-changelog.rst"),
            os.path.join(doc_dir, "hardware", "slaves", "16-adi-ades1830-vx.x.x", "16-adi-ades1830-v0.9.0", "16-adi-ades1830-v0.9.0_cell_voltage-sense-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "16-adi-ades1830-vx.x.x", "16-adi-ades1830-v0.9.0", "16-adi-ades1830-v0.9.0_daisy-input-connectors.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "16-adi-ades1830-vx.x.x", "16-adi-ades1830-v0.9.0", "16-adi-ades1830-v0.9.0_daisy-output-connectors.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "16-adi-ades1830-vx.x.x", "16-adi-ades1830-v0.9.0", "16-adi-ades1830-v0.9.0_electrical-ratings.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "16-adi-ades1830-vx.x.x", "16-adi-ades1830-v0.9.0", "16-adi-ades1830-v0.9.0_mechanical-dimensions.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "16-adi-ades1830-vx.x.x", "16-adi-ades1830-v0.9.0", "16-adi-ades1830-v0.9.0_temperature-sensor-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "16-adi-ades1830-vx.x.x", "16-adi-ades1830-v0.9.0.rst"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-changelog.rst"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.3", "18-ltc-ltc6813-1-v1.1.3_analog-inputs-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.3", "18-ltc-ltc6813-1-v1.1.3_cell_voltage-sense-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.3", "18-ltc-ltc6813-1-v1.1.3_daisy-input-connectors.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.3", "18-ltc-ltc6813-1-v1.1.3_daisy-output-connectors.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.3", "18-ltc-ltc6813-1-v1.1.3_digital-io-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.3", "18-ltc-ltc6813-1-v1.1.3_electrical-ratings.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.3", "18-ltc-ltc6813-1-v1.1.3_extension-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.3", "18-ltc-ltc6813-1-v1.1.3_extension-connector_2.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.3", "18-ltc-ltc6813-1-v1.1.3_external-dc-supply-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.3", "18-ltc-ltc6813-1-v1.1.3_gpio-extension-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.3", "18-ltc-ltc6813-1-v1.1.3_mechanical-dimensions.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.3", "18-ltc-ltc6813-1-v1.1.3_temperature-sensor-connector.csv"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.3.rst"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.4.rst"),
            os.path.join(doc_dir, "hardware", "slaves", "18-ltc-ltc6813-1-vx.x.x", "18-ltc-ltc6813-1-v1.1.5.rst"),
            os.path.join(doc_dir, "hardware", "slaves.rst"),
            os.path.join(doc_dir, "index.rst"),
            os.path.join(doc_dir, "introduction", "abbreviations-definitions.rst"),
            os.path.join(doc_dir, "introduction", "bms-overview.rst"),
            os.path.join(doc_dir, "introduction", "naming-conventions.rst"),
            os.path.join(doc_dir, "introduction", "use-case.rst"),
            os.path.join(doc_dir, "macros.txt"),
            os.path.join(doc_dir, "misc", "acknowledgements.rst"),
            os.path.join(doc_dir, "misc", "bibliography.rst"),
            os.path.join(doc_dir, "misc", "definitions.csv"),
            os.path.join(doc_dir, "misc", "developer-manual-nomenclature.csv"),
            os.path.join(doc_dir, "misc", "indices-and-tables.rst"),
            os.path.join(doc_dir, "software", "api", "overview.rst"),
            os.path.join(doc_dir, "software", "architecture", "architecture.rst"),
            os.path.join(doc_dir, "software", "build", "build.rst"),
            os.path.join(doc_dir, "software", "build", "waf-available-commands.csv"),
            os.path.join(doc_dir, "software", "build-environment", "build-environment.rst"),
            os.path.join(doc_dir, "software", "build-environment", "build-environment_how-to.rst"),
            os.path.join(doc_dir, "software", "build-process", "build-process.rst"),
            os.path.join(doc_dir, "software", "build-process", "library-project_how-to.rst"),
            os.path.join(doc_dir, "software", "configuration", "battery-cell-configuration.csv"),
            os.path.join(doc_dir, "software", "configuration", "battery-system-configuration.csv"),
            os.path.join(doc_dir, "software", "configuration", "compiler", "compiler-configuration.rst"),
            os.path.join(doc_dir, "software", "configuration", "compiler", "linker_pulls", "example_linker_output.txt"),
            os.path.join(doc_dir, "software", "configuration", "configuration.rst"),
            os.path.join(doc_dir, "software", "configuration", "fstartup.c-check.txt"),
            os.path.join(doc_dir, "software", "configuration", "without-halcogen_how-to.rst"),
            os.path.join(doc_dir, "software", "how-to", "how-to.rst"),
            os.path.join(doc_dir, "software", "linker-script", "linker-script.rst"),
            os.path.join(doc_dir, "software", "linker-script", "linker-script-definitions.csv"),
            os.path.join(doc_dir, "software", "modules", "application", "algorithm", "algorithm.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "algorithm", "state-estimation", "soc", "soc_counting.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "algorithm", "state-estimation", "soc", "soc_debug.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "algorithm", "state-estimation", "soc", "soc_none.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "algorithm", "state-estimation", "soe", "soe_counting.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "algorithm", "state-estimation", "soe", "soe_debug.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "algorithm", "state-estimation", "soe", "soe_none.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "algorithm", "state-estimation", "sof", "sof_trapezoid.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "algorithm", "state-estimation", "soh", "soh_debug.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "algorithm", "state-estimation", "soh", "soh_none.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "algorithm", "state-estimation", "state-estimation.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "bal", "bal.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "bms", "bms.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "bms", "bms_how-to.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "plausibility", "plausibility.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "redundancy", "redundancy.rst"),
            os.path.join(doc_dir, "software", "modules", "application", "soa", "soa.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "adc", "adc.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "afe", "adding-a-new-ic_how-to.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "afe", "adi", "adi_ades1830.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "afe", "afe.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "afe", "debug", "default.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "afe", "ltc", "6804-1.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "afe", "ltc", "6806.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "afe", "ltc", "6811-1.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "afe", "ltc", "6812-1.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "afe", "ltc", "6813-1.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "afe", "maxim", "max1785x.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "afe", "nxp", "mc33775a.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "afe", "supported-afes.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "can", "can.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "can", "can_how-to.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "contactor", "contactor.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "crc", "crc.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "dma", "dma.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "foxmath", "foxmath.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "foxmath", "utils.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "fram", "fram.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "htsen", "htsen.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "i2c", "i2c.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "imd", "bender", "bender_ir155.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "imd", "bender", "bender_iso165c.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "imd", "imd.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "imd", "none", "no-imd.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "interlock", "interlock.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "io", "io.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "mcu", "mcu.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "meas", "meas.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "pex", "pex.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "pwm", "pwm.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "rtc", "rtc.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "sbc", "sbc.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "spi", "spi.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "sps", "sps.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "ts", "adding-a-new-ts_how-to.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "ts", "epcos", "b57251v5103j060.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "ts", "epcos", "b57861s0103f045.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "ts", "fake", "none.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "ts", "murata", "ncxxxxh103.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "ts", "ts.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "ts", "ts-sensors.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "ts", "ts-short-names.csv"),
            os.path.join(doc_dir, "software", "modules", "driver", "ts", "vishay", "ntcalug01a103g.rst"),
            os.path.join(doc_dir, "software", "modules", "driver", "ts", "vishay", "ntcle317e4103sba.rst"),
            os.path.join(doc_dir, "software", "modules", "engine", "database", "database.rst"),
            os.path.join(doc_dir, "software", "modules", "engine", "database", "database_how-to.rst"),
            os.path.join(doc_dir, "software", "modules", "engine", "diag", "diag.rst"),
            os.path.join(doc_dir, "software", "modules", "engine", "diag", "diag_how-to.rst"),
            os.path.join(doc_dir, "software", "modules", "engine", "hw_info", "hw_info.rst"),
            os.path.join(doc_dir, "software", "modules", "engine", "sys", "sys.rst"),
            os.path.join(doc_dir, "software", "modules", "engine", "sys_mon", "sys_mon.rst"),
            os.path.join(doc_dir, "software", "modules", "main", "fassert.rst"),
            os.path.join(doc_dir, "software", "modules", "main", "fassert_how-to.rst"),
            os.path.join(doc_dir, "software", "modules", "main", "startup.rst"),
            os.path.join(doc_dir, "software", "modules", "main", "version.rst"),
            os.path.join(doc_dir, "software", "modules", "modules.rst"),
            os.path.join(doc_dir, "software", "modules", "task", "ftask", "ftask.rst"),
            os.path.join(doc_dir, "software", "modules", "task", "ftask", "ftask_how-to.rst"),
            os.path.join(doc_dir, "software", "modules", "task", "ftask", "ftask-function-overview.csv"),
            os.path.join(doc_dir, "software", "modules", "task", "ftask", "ftask-user-code-functions.csv"),
            os.path.join(doc_dir, "software", "modules", "task", "os", "os.rst"),
            os.path.join(doc_dir, "software", "overview", "sw-overview.rst"),
            os.path.join(doc_dir, "software", "unit-tests", "unit-tests.rst"),
            os.path.join(doc_dir, "software", "unit-tests", "unit-tests_how-to.rst"),
            os.path.join(doc_dir, "spelling_wordlist.txt"),
            os.path.join(doc_dir, "system", "bjb-measurements.csv"),
            os.path.join(doc_dir, "system", "colors-in-system-block-diagram.csv"),
            os.path.join(doc_dir, "system", "precharging.rst"),
            os.path.join(doc_dir, "system", "symbols-in-system-block-diagram.csv"),
            os.path.join(doc_dir, "system", "system-introduction.rst"),
            os.path.join(doc_dir, "system", "system-voltage-and-current-monitoring.rst"),
            os.path.join(doc_dir, "tools", "dbc.rst"),
            os.path.join(doc_dir, "tools", "debugger", "debug-application.rst"),
            os.path.join(doc_dir, "tools", "debugger", "debugger-lauterbach.rst"),
            os.path.join(doc_dir, "tools", "debugger", "debugger-ozone.rst"),
            os.path.join(doc_dir, "tools", "gui", "gui.rst"),
            os.path.join(doc_dir, "tools", "gui", "gui-implementation.rst"),
            os.path.join(doc_dir, "tools", "gui", "impl", "entry", "entry.rst"),
            os.path.join(doc_dir, "tools", "gui", "impl", "fgui.rst"),
            os.path.join(doc_dir, "tools", "gui", "impl", "log_parser", "log_parser.rst"),
            os.path.join(doc_dir, "tools", "gui", "impl", "lvac", "lvac.rst"),
            os.path.join(doc_dir, "tools", "gui", "impl", "misc", "misc.rst"),
            os.path.join(doc_dir, "tools", "gui", "impl", "workers", "workers.rst"),
            os.path.join(doc_dir, "tools", "halcogen", "halcogen.rst"),
            os.path.join(doc_dir, "tools", "static-analysis", "axivion.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "compiler-tool", "f_ti_arm_cgt.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "compiler-tool", "f_ti_arm_cgt_cc_options.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "compiler-tool", "f_ti_arm_helper.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "compiler-tool", "f_ti_arm_tools.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "compiler-tool", "f_ti_color_arm_cgt.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_axivion.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_black.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_bootstrap_library_project.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_check_db_vars.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_clang_format.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_git_hooks.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_guidelines.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_hcg.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_j_flash.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_lauterbach.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_miniconda_env.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_node_helper.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_ozone.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_pylint.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_sphinx_build.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_unit_test.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "f_vscode.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "ti-arm-compiler-tools.csv"),
            os.path.join(doc_dir, "tools", "waf-tools", "ti-arm-compiler-tools.rst"),
            os.path.join(doc_dir, "tools", "waf-tools", "waf-tools.rst"),
            os.path.join(doc_dir, "units.txt"),
        ]
        # pylint: enable=line-too-long
        # fmt: on
        source = []
        for src in sources:
            node = bld.path.find_node(src)
            if not node:
                bld.fatal(f"{src} does not exist.")
            source.append(node)
        config = bld.path.find_node(os.path.join("docs", "conf.py"))
        bld(
            features="sphinx",
            builders="html spelling",
            out_dir=".",
            source=source,
            conf_py=config,
            VERSION=bld.env.version,
            RELEASE=bld.env.version,
        )


def build_all(ctx):  # pylint: disable=unused-argument
    """shortcut to build all variants"""
    # axivion, if existing, needs to be inserted at the end of build commands
    has_ax = ""
    for bld_var in BUILD_VARIANTS:
        if "axivion" in bld_var:
            has_ax = "axivion"
            continue
        Options.commands.append(bld_var)
    if has_ax:
        Options.commands.append("build_axivion")


def clean_all(ctx):  # pylint: disable=unused-argument
    """shortcut to clean all variants"""
    # axivion, if existing, needs to be inserted at the end of clean commands
    has_ax = ""
    for i in CLEAN_VARIANTS:
        if "axivion" in i:
            has_ax = "axivion"
            continue
        Options.commands.append(i)
    if has_ax:
        Options.commands.append("clean_axivion")


def dist(conf):
    """creates a archive based on the current repository state"""
    conf.base_name = APPNAME.lower()
    conf.algo = "tar.gz"
    conf.excl = DIST_EXCLUDE


def distcheck_cmd(self):  # pylint: disable=unused-argument,missing-function-docstring
    # overwrite distcheck_cmd
    cfg = []
    if Options.options.distcheck_args:
        cfg = shlex.split(Options.options.distcheck_args)
    else:
        cfg = [x for x in sys.argv if x.startswith("-")]
    if "-c" in cfg and not "yes" in cfg:
        cfg.insert(cfg.index("-c") + 1, "yes")
    dist_waf = os.path.relpath(sys.argv[0], self.path.abspath())
    cmd = [
        sys.executable,
        os.path.join(self.path.abspath(), self.get_base_name(), dist_waf),
        "configure",
        "build_all",
    ] + cfg
    return cmd


def check_cmd(self):  # pylint: disable=missing-function-docstring
    # overwrite check_cmd
    full_arch = self.get_arch_name().split(self.algo)[0]
    if full_arch.endswith("."):
        full_arch = full_arch[:-1]
    full_arch = f"{full_arch}-build.tar.gz"
    with tarfile.open(self.get_arch_name()) as _tarfile:
        for _file in _tarfile:
            _tarfile.extract(_file)
    cmd = self.make_distcheck_cmd()
    ret = Utils.subprocess.Popen(cmd, cwd=self.get_base_name()).wait()
    if ret:
        raise Errors.WafError(f"distcheck failed with code {ret}")
    if getattr(self, "tar_build", False):
        with tarfile.open(full_arch, "w:gz") as tar:
            tar.add(
                self.get_arch_name(), arcname=os.path.basename(self.get_arch_name())
            )
            tar.add(
                os.path.join(APPNAME.lower(), out),
                arcname=os.path.join(APPNAME.lower(), out),
            )


def distcheck(conf):
    """creates tar.bz form the source directory and tries to run a build"""
    Scripting.DistCheck.make_distcheck_cmd = distcheck_cmd
    Scripting.DistCheck.check = check_cmd
    conf.base_name = APPNAME.lower()
    conf.excl = DIST_EXCLUDE


class DistCheckBin(Scripting.DistCheck):
    """Wrapper class to create a distcheck run that includes the build folder"""

    fun = "distcheck_bin"
    cmd = "distcheck_bin"


def distcheck_bin(conf):
    """creates tar.bz form the source directory and tries to run a build, and
    includes the build output in the archive"""
    conf.tar_build = True
    Scripting.DistCheck.make_distcheck_cmd = distcheck_cmd
    Scripting.DistCheck.check = check_cmd
    conf.base_name = APPNAME.lower()
    conf.excl = DIST_EXCLUDE


def check_test_files(ctx):
    """Check if test files to corresponding source files exist."""
    prefix = os.path.join(ctx.path.abspath(), "src") + os.pathsep
    sources = [
        i.abspath()[len(prefix) :]
        for i in ctx.path.ant_glob(
            "src/app/**/*.c src/opt/**/*.c",
            excl=[
                "src/app/driver/sbc/fs8x_driver/**",
                "src/app/driver/afe/nxp/mc33775a/vendor/**",
                "src/hal/**",
                "src/os/**",
            ],
        )
    ]

    prefix = os.path.join(ctx.path.abspath(), "tests", "unit") + os.pathsep
    tests = [
        i.abspath()[len(prefix) :].replace("test_", "")
        for i in ctx.path.ant_glob("tests/unit/**/test_*.c")
    ]
    diff = set(sources) - set(tests)
    err_msg = ""
    for i in diff:
        test_file = os.path.join(
            ctx.path.abspath(), "tests", "unit", f"test_{pathlib.Path(i).name}"
        )
        test_file = os.path.join(
            "tests",
            "unit",
            i.replace(pathlib.Path(i).name, f"test_{pathlib.Path(i).name}"),
        )
        err_msg += f"Missing test file for:  {i} (should be in: {test_file})\n"
    if diff:
        ctx.fatal(f"{err_msg}\nTest files are missing.")

    err_msg = ""
    for test in ctx.path.ant_glob("tests/unit/**/test_*.c"):
        for i, line in enumerate(test.read(encoding="utf-8").splitlines()):
            if line.startswith("void test"):
                if not line.endswith("(void) {"):
                    err_msg += (
                        f"{test.abspath()}:{i+1}: Test files need to have "
                        f"the form 'test<TestName> (void) {{' ({line}\n"
                    )
    if err_msg:
        ctx.fatal(f"{err_msg}\nTests are implement invalid.")


def get_axivion_files(ctx):
    """get all relevant test files. The output is meant to be used in
    tests/axivion/qualification-test/run_axivion_qualification_kit_tests.bat"""
    as_seen_from = ctx.path.find_node("tests/axivion/qualification-test")
    all_test_files = ctx.path.ant_glob(
        [
            "tests/axivion/qualification-test/qualification-kit/**/*.c",
            "tests/axivion/qualification-test/qualification-kit/**/*.tst",
        ],
    )
    tests = []
    for i in all_test_files:
        real_path = pathlib.Path(i.parent.path_from(as_seen_from)).as_posix().split("/")
        g_path = "".join(["*/" for _ in real_path])
        if g_path not in tests:
            tests.append(g_path)
    resulting_glob = []
    for i in tests:
        resulting_glob.extend([f"{i}*.c", f"{i}*.tst"])
    print(" ".join(sorted(resulting_glob)))


def get_deepest_src_file(ctx):
    """Returns the path length of all source files."""
    all_test_files = ctx.path.ant_glob("src/**")
    sorted_files = sorted(all_test_files, key=lambda x: len(x.path_from(ctx.path)))
    for i in sorted_files:
        print(len(i.abspath()), i.abspath())
